﻿using AIStudio.ConSole.Redis.Core;
using CSRedis;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace AIStudio.ConSole.Redis.Ch06
{
    class TestCh06
    {
        public void add_update_contact(CSRedisClient conn, string user, string contact)
        {
            var ac_list = "recent:" + user;
            var pipeline = conn.StartPipe();
            pipeline.LRem(ac_list, 1, contact);
            pipeline.LPush(ac_list, contact);
            pipeline.LTrim(ac_list, 0, 99);
            pipeline.EndPipe();
        }

        public void remove_contact(CSRedisClient conn, string user, string contact)
        {
            conn.LRem("recent:" + user, 1, contact);
        }

        public List<string> fetch_autocomplete_list(CSRedisClient conn, string user, string prefix)
        {
            var candidates = conn.LRange("recent:" + user, 0, -1);
            List<string> matches = new List<string>();
            foreach (var candidate in candidates)
            {
                if (candidate.ToLower().StartsWith(prefix.ToLower()))
                {
                    matches.Add(candidate);
                }
            }
            return matches;
        }

        string valid_characters = "`abcdefghijklmnopqrstuvwxyz{";

        public string find_prefix_range(string prefix)
        {
            int posn = valid_characters.IndexOf(prefix[prefix.Length - 1]);
            var suffix = valid_characters[posn - 1];

            return prefix.Substring(0, prefix.Length - 1) + suffix.ToString() + "{," + prefix + "{";
        }

        public List<string> autocomplete_on_prefix(CSRedisClient conn, string guild, string prefix)
        {
            List<string> items = new List<string>();
            var startend = find_prefix_range(prefix).Split(",");
            var identifier = Guid.NewGuid().ToString();
            var start = startend[0] + identifier;
            var end = startend[1] + identifier;
            var zset_name = "members:" + guild;

            conn.ZAdd(zset_name, (0, start), (0, end));
            var pipeline = conn.StartPipe();

            while (1 == 1)
            {
                try
                {
                    var sindex = conn.ZRank(zset_name, start);
                    var eindex = conn.ZRank(zset_name, end);
                    var erange = Math.Min(sindex.Value + 9, eindex.Value - 2);

                    pipeline.ZRem(zset_name, start, end);
                    pipeline.ZRange(zset_name, sindex ?? 0, erange);

                    var objects = pipeline.EndPipe();
                    items = new List<string>(objects[1] as string[]);
                    break;
                }
                catch
                {

                }
            }

            return items;

        }

        public void join_guild(CSRedisClient conn, string guild, string user)
        {
            conn.ZAdd("members:" + guild, (0, user));
        }

        public void leave_guild(CSRedisClient conn, string guild, string user)
        {
            conn.ZRem("members:" + guild, user);
        }

        public void test_address_book_autocomplete(CSRedisClient conn)
        {
            conn.Del("members:test");
            Console.WriteLine($"the start/end range of 'abc' is:{find_prefix_range("abc")}");
            Console.WriteLine();

            Console.WriteLine("Let's add a few people to the guild");
            foreach (var name in new string[] { "jeff", "jenny", "jack", "jennifer" })
            {
                join_guild(conn, "test", name);
            }

            Console.WriteLine();
            Console.WriteLine("now let's try to find users with names starting with 'je':");
            var r = autocomplete_on_prefix(conn, "test", "je");
            Console.WriteLine(PrintHelper.StringArray2String(r.ToArray()));

            Console.WriteLine("jeff just left to join a different guild...");
            leave_guild(conn, "test", "jeff");
            r = autocomplete_on_prefix(conn, "test", "je");
            Console.WriteLine(PrintHelper.StringArray2String(r.ToArray()));
            conn.Del("members:test");
        }

        public string acquire_lock(CSRedisClient conn, string lockname, int acquire_timeout = 10)
        {
            var identifier = Guid.NewGuid().ToString();
            var end = DateTime.Now.AddSeconds(10);
            while (DateTime.Now < end)
            {
                if (conn.SetNx("lock:" + lockname, identifier))
                    return identifier;

                Thread.Sleep(1);
            }

            return null;
        }

        public bool release_lock(CSRedisClient conn, string lockname, string identifier)
        {

            lockname = "lock:" + lockname;
            while (true)
            {
                try
                {
                    if (conn.Get(lockname) == identifier)
                    {
                        var pipe = conn.StartPipe();
                        pipe.Del(lockname);
                        pipe.EndPipe();
                        return true;
                    }
                }
                catch
                {
                    return false;
                }
            }
        }

        private bool purchase_item_with_lock(CSRedisClient conn, string buyerid, string itemid, string sellerid)
        {
            var buyer = $"users:{buyerid}";
            var seller = $"users:{sellerid}";
            var item = $"{itemid}.{sellerid}";
            var inventory = $"inventory:{sellerid}";

            var locked = acquire_lock(conn, "market:");
            if (string.IsNullOrEmpty(locked))
                return false;

            var pipe = conn.StartPipe();
            try
            {
                pipe.ZScore("market:", item);
                pipe.HGet(buyer, "funds");
                var pricefund = pipe.EndPipe();
                var price = (decimal)pricefund[0];
                var funds = (decimal)pricefund[1];
                if (price <= 0 || price > funds)
                    return false;

                pipe.HIncrBy(seller, "funds", (int)price);
                pipe.HIncrBy(buyer, "funds", (int)-price);
                pipe.SAdd(inventory, itemid);
                pipe.ZRem("market:", item);
                pipe.EndPipe();
                return true;
            }
            catch { return false; }
            finally
            {
                release_lock(conn, "market:", locked);
            }
        }

        public void Test()
        {
            var conn = new CSRedis.CSRedisClient("127.0.0.1:6379,defaultDatabase=0,poolsize=500,ssl=false,writeBuffer=10240");
            test_address_book_autocomplete(conn);
        }

    }
}
